import os
import sys
import time
import logging
import win32print
import win32api
import configparser
import hashlib
import json
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
from datetime import datetime

# 初始化日志配置
def setup_logging():
    log_dir = "logs"
    os.makedirs(log_dir, exist_ok=True)
    log_file = os.path.join(log_dir, f"print_service_{datetime.now().strftime('%Y%m%d')}.log")
    
    logging.basicConfig(
        level=logging.INFO,
        format='%(asctime)s - %(levelname)s - %(message)s',
        handlers=[
            logging.FileHandler(log_file),
            logging.StreamHandler()
        ]
    )

# 加载配置文件
def load_config():
    config = configparser.ConfigParser()
    try:
        if not os.path.exists('config.ini'):
            raise FileNotFoundError("配置文件 config.ini 不存在")
            
        config.read('config.ini', encoding='utf-8')
        
        # 读取配置并设置默认值
        supported_extensions = config.get('Settings', 'supported_extensions', fallback='.pdf,.doc,.docx').split(',')
        default_printer = config.get('Settings', 'default_printer', fallback=win32print.GetDefaultPrinter())
        watch_folder = config.get('Settings', 'watch_folder', fallback='./watch_folder')
        temp_folder = config.get('Settings', 'temp_folder', fallback='./temp_prints')
        max_retries = config.getint('Settings', 'max_retries', fallback=3)
        retry_interval = config.getint('Settings', 'retry_interval', fallback=5)
        
        return {
            'supported_extensions': [ext.strip().lower() for ext in supported_extensions],
            'default_printer': default_printer,
            'watch_folder': watch_folder,
            'temp_folder': temp_folder,
            'max_retries': max_retries,
            'retry_interval': retry_interval
        }
    except Exception as e:
        logging.error(f"加载配置失败: {str(e)}")
        sys.exit(1)

class PrintHandler(FileSystemEventHandler):
    """监视文件夹并打印新文件"""
    
    def __init__(self, config):
        super().__init__()
        self.supported_extensions = config['supported_extensions']
        self.default_printer = config['default_printer']
        self.temp_folder = config['temp_folder']
        self.max_retries = config['max_retries']
        self.retry_interval = config['retry_interval']
        self.processed_files = set()  # 记录已处理的文件路径
        self.printed_files = set()    # 记录已打印的文件路径
        self.file_hashes = {}         # 记录文件哈希值，防止重复打印
        
        # 确保临时文件夹和状态文件夹存在
        os.makedirs(self.temp_folder, exist_ok=True)
        os.makedirs("print_status", exist_ok=True)
        
        # 加载已处理和已打印的文件记录
        self.load_print_status()
    
    def load_print_status(self):
        """从状态文件加载已处理和已打印的文件记录"""
        try:
            # 加载已处理文件记录
            processed_file = "print_status/processed_files.json"
            if os.path.exists(processed_file):
                with open(processed_file, 'r', encoding='utf-8') as f:
                    processed_data = json.load(f)
                    self.processed_files = set(processed_data.get('files', []))
                    logging.info(f"已加载 {len(self.processed_files)} 个已处理文件记录")
                    
            # 加载已打印文件记录
            printed_file = "print_status/printed_files.json"
            if os.path.exists(printed_file):
                with open(printed_file, 'r', encoding='utf-8') as f:
                    printed_data = json.load(f)
                    self.printed_files = set(printed_data.get('files', []))
                    logging.info(f"已加载 {len(self.printed_files)} 个已打印文件记录")
                    
            # 加载文件哈希记录
            hash_file = "print_status/file_hashes.json"
            if os.path.exists(hash_file):
                with open(hash_file, 'r', encoding='utf-8') as f:
                    self.file_hashes = json.load(f)
                    logging.info(f"已加载 {len(self.file_hashes)} 个文件哈希记录")
        except Exception as e:
            logging.error(f"加载打印状态记录失败: {str(e)}")
            # 继续使用空记录集，不会影响服务运行
    
    def save_print_status(self):
        """保存已处理和已打印的文件记录到状态文件"""
        try:
            # 保存已处理文件记录
            processed_file = "print_status/processed_files.json"
            with open(processed_file, 'w', encoding='utf-8') as f:
                json.dump({'files': list(self.processed_files)}, f, ensure_ascii=False, indent=2)
                
            # 保存已打印文件记录
            printed_file = "print_status/printed_files.json"
            with open(printed_file, 'w', encoding='utf-8') as f:
                json.dump({'files': list(self.printed_files)}, f, ensure_ascii=False, indent=2)
                
            # 保存文件哈希记录
            hash_file = "print_status/file_hashes.json"
            with open(hash_file, 'w', encoding='utf-8') as f:
                json.dump(self.file_hashes, f, ensure_ascii=False, indent=2)
                
            logging.info("已保存打印状态记录")
        except Exception as e:
            logging.error(f"保存打印状态记录失败: {str(e)}")
    
    def on_created(self, event):
        if not event.is_directory:
            filepath = event.src_path
            filename = os.path.basename(filepath)
            
            # 获取文件的绝对路径，确保路径格式统一
            filepath = os.path.abspath(filepath)
            
            # 防止重复处理
            if filepath in self.processed_files:
                logging.info(f"文件已处理过，跳过: {filepath}")
                return
                
            self.processed_files.add(filepath)
            
            # 保存已处理文件记录
            self.save_print_status()
            
            # 检查文件扩展名
            ext = os.path.splitext(filename)[1].lower()
            if ext in self.supported_extensions:
                logging.info(f"发现可打印文件: {filename}")
                self.process_file(filepath)
    
    def calculate_file_hash(self, filepath):
        """计算文件的MD5哈希值"""
        try:
            if not os.path.exists(filepath):
                return None
                
            hasher = hashlib.md5()
            with open(filepath, 'rb') as f:
                buf = f.read(65536)  # 读取64kb块
                while len(buf) > 0:
                    hasher.update(buf)
                    buf = f.read(65536)
            return hasher.hexdigest()
        except Exception as e:
            logging.error(f"计算文件哈希值失败: {str(e)}")
            return None
    
    def process_file(self, filepath):
        """处理文件打印"""
        filename = os.path.basename(filepath)
        temp_path = os.path.join(self.temp_folder, filename)
        
        try:
            # 复制文件到临时目录（防止原始文件被占用）
            if os.path.exists(filepath):
                # 检查文件权限和路径编码
                try:
                    # 获取文件绝对路径
                    filepath = os.path.abspath(filepath)
                    # 统一使用utf-8编码处理路径
                    try:
                        filepath = filepath.encode('utf-8').decode('utf-8')
                    except UnicodeEncodeError:
                        # 如果utf-8失败，尝试使用系统默认编码
                        filepath = filepath.encode(sys.getfilesystemencoding()).decode(sys.getfilesystemencoding())
                    # 添加详细的权限检查日志
                    logging.info(f'正在检查文件权限: {filepath}')
                    logging.info(f'当前用户: {os.getlogin()}')
                    logging.info(f'文件所有者: {os.stat(filepath).st_uid}')
                    logging.info(f'文件权限: {oct(os.stat(filepath).st_mode)[-3:]}')
                    
                    # 检查文件是否存在且有读取权限
                    if not os.path.exists(filepath):
                        logging.error(f"文件 {filename} 不存在")
                        return
                    if not os.access(filepath, os.R_OK):
                        logging.error(f"文件 {filename} 没有读取权限")
                        # 尝试获取文件权限
                        try:
                            os.chmod(filepath, 0o644)
                            if not os.access(filepath, os.R_OK):
                                return
                        except Exception as e:
                            logging.error(f"无法设置文件 {filename} 的权限: {str(e)}")
                            return
                except Exception as e:
                    logging.error(f"处理文件路径 {filename} 时出错: {str(e)}")
                    return
                
                # 复制文件
                with open(filepath, 'rb') as src, open(temp_path, 'wb') as dst:
                    dst.write(src.read())
                    logging.info(f'成功将文件复制到临时目录: {temp_path}')
                    logging.info(f'临时文件大小: {os.path.getsize(temp_path)} 字节')
                
                # 计算文件哈希值
                file_hash = self.calculate_file_hash(temp_path)
                if file_hash:
                    hash_str = str(file_hash)  # 确保哈希值是字符串类型作为JSON的键
                    # 检查是否已经处理过相同内容的文件
                    if hash_str in self.file_hashes:
                        previous_filepath = self.file_hashes[hash_str]['filepath']
                        previous_time = self.file_hashes[hash_str]['time']
                        logging.info(f"检测到重复文件: {filename} 与 {os.path.basename(previous_filepath)} 内容相同，跳过打印")
                        logging.info(f"该内容之前已于 {previous_time} 打印过")
                        
                        # 添加到已打印文件记录，避免再次处理
                        self.printed_files.add(filepath)
                        self.save_print_status()
                        
                        # 删除临时文件
                        try:
                            os.remove(temp_path)
                        except Exception as e:
                            logging.warning(f"删除临时文件失败: {str(e)}")
                        return
                
                # 检查文件是否已经打印过
                if filepath in self.printed_files:
                    logging.info(f"文件 {filename} 已打印过，跳过打印")
                    # 删除临时文件
                    try:
                        os.remove(temp_path)
                    except Exception as e:
                        logging.warning(f"删除临时文件失败: {str(e)}")
                    return
                
                # 尝试打印
                for attempt in range(1, self.max_retries + 1):
                    if self.print_file(temp_path):
                        # 打印成功，记录文件哈希值
                        if file_hash:
                            hash_str = str(file_hash)
                            self.file_hashes[hash_str] = {
                                'filepath': filepath,
                                'time': datetime.now().isoformat(),
                                'filename': filename
                            }
                        
                        # 添加到已打印文件记录
                        self.printed_files.add(filepath)
                        self.save_print_status()
                        
                        logging.info(f"成功打印文件: {filename}")
                        break
                    else:
                        if attempt < self.max_retries:
                            logging.warning(f"打印失败，将在 {self.retry_interval} 秒后重试 (尝试 {attempt}/{self.max_retries})")
                            time.sleep(self.retry_interval)
                else:
                    logging.error(f"无法打印文件: {filename} (达到最大重试次数)")
                
                # 删除临时文件
                try:
                    os.remove(temp_path)
                except Exception as e:
                    logging.warning(f"删除临时文件失败: {str(e)}")
            
        except Exception as e:
            logging.error(f"处理文件 {filename} 时出错: {str(e)}")
            
            # 尝试清理临时文件
            try:
                if os.path.exists(temp_path):
                    os.remove(temp_path)
            except Exception:
                pass
    
    def print_file(self, filepath):
        """执行文件打印"""
        try:
            # 检查文件是否存在且可读
            if not os.path.exists(filepath):
                logging.error(f"文件不存在: {filepath}")
                return False
            if not os.access(filepath, os.R_OK):
                logging.error(f"文件不可读: {filepath}")
                return False

            # 检查打印机是否可用
            if not self.is_printer_available():
                logging.error(f"打印机不可用: {self.default_printer}")
                return False

            # 记录打印开始时间
            print_start_time = datetime.now()
            logging.info(f"开始打印文件: {os.path.basename(filepath)}, 时间: {print_start_time.isoformat()}")

            # 使用ShellExecute进行打印
            try:
                result = win32api.ShellExecute(0, "print", filepath, None, ".", 0)
                if result <= 32:
                    logging.error(f"打印失败，错误代码: {result}")
                    logging.error(f"详细错误信息: {win32api.GetLastError()}")
                    return False
                # 检查打印任务是否成功加入队列
                if not self.check_print_job():
                    logging.error("打印任务未成功加入队列")
                    return False
                
                # 记录打印结束时间
                print_end_time = datetime.now()
                logging.info(f"文件打印成功: {os.path.basename(filepath)}, 结束时间: {print_end_time.isoformat()}")
                logging.info(f"打印耗时: {(print_end_time - print_start_time).total_seconds()} 秒")
                
                return True
            except Exception as e:
                logging.error(f"打印错误: {str(e)}")
                return False
        except Exception as e:
            logging.error(f"打印错误: {str(e)}")
            return False
    
    def is_printer_available(self):
        """检查打印机是否可用"""
        try:
            # 获取默认打印机
            printer_name = self.default_printer
            if not printer_name:
                printer_name = win32print.GetDefaultPrinter()
                
            if not printer_name:
                logging.error("无法获取默认打印机名称")
                return False
                
            # 查询打印机状态
            handle = win32print.OpenPrinter(printer_name)
            if not handle:
                logging.error(f"无法打开打印机: {printer_name}")
                return False
                
            try:
                printer_info = win32print.GetPrinter(handle, 2)
                status = printer_info['Status']
                
                # 检查打印机状态
                if status == 0:  # 打印机正常
                    logging.info(f"打印机 {printer_name} 状态正常")
                    return True
                else:
                    # 打印机存在问题
                    status_text = "未知状态"
                    if status & win32print.PRINTER_STATUS_PAUSED:
                        status_text = "已暂停"
                    elif status & win32print.PRINTER_STATUS_ERROR:
                        status_text = "错误状态"
                    elif status & win32print.PRINTER_STATUS_PENDING_DELETION:
                        status_text = "正在删除"
                    elif status & win32print.PRINTER_STATUS_PAPER_JAM:
                        status_text = "卡纸"
                    elif status & win32print.PRINTER_STATUS_PAPER_OUT:
                        status_text = "缺纸"
                    elif status & win32print.PRINTER_STATUS_MANUAL_FEED:
                        status_text = "手动进纸"
                    elif status & win32print.PRINTER_STATUS_PAPER_PROBLEM:
                        status_text = "纸张问题"
                    elif status & win32print.PRINTER_STATUS_OFFLINE:
                        status_text = "离线"
                    elif status & win32print.PRINTER_STATUS_IO_ACTIVE:
                        status_text = "IO活动"
                    elif status & win32print.PRINTER_STATUS_BUSY:
                        status_text = "忙"
                    elif status & win32print.PRINTER_STATUS_OUTPUT_BIN_FULL:
                        status_text = "出纸盒已满"
                    elif status & win32print.PRINTER_STATUS_WARMING_UP:
                        status_text = "预热中"
                    elif status & win32print.PRINTER_STATUS_TONER_LOW:
                        status_text = "墨粉不足"
                    elif status & win32print.PRINTER_STATUS_NO_TONER:
                        status_text = "缺少墨粉"
                    
                    logging.error(f"打印机 {printer_name} 状态异常: {status_text} (代码: {status})")
                    return False
            finally:
                win32print.ClosePrinter(handle)
                
        except Exception as e:
            logging.error(f"检查打印机状态时出错: {str(e)}")
            return False
    
    def check_print_job(self):
        """检查打印任务是否已成功添加到队列"""
        try:
            # 获取默认打印机名称
            printer_name = self.default_printer
            if not printer_name:
                printer_name = win32print.GetDefaultPrinter()
                
            if not printer_name:
                logging.error("无法获取默认打印机名称")
                return False
                
            # 打开打印机并查询作业
            handle = win32print.OpenPrinter(printer_name)
            if not handle:
                logging.error(f"无法打开打印机: {printer_name}")
                return False
                
            try:
                # 尝试获取打印队列中的作业数量
                jobs = win32print.EnumJobs(handle, 0, 999)
                logging.info(f"打印队列中有 {len(jobs)} 个作业")
                
                # 如果有作业，认为添加成功
                # 注意：这只是一种近似判断，不是100%准确
                # 因为我们无法确定刚刚添加的具体是哪个作业
                return True
            finally:
                win32print.ClosePrinter(handle)
                
        except Exception as e:
            logging.error(f"检查打印作业时出错: {str(e)}")
            return False

def run_service():
    """启动打印服务"""
    # 设置日志
    setup_logging()
    logging.info("正在启动云打印服务...")
    
    # 加载配置
    config = load_config()
    logging.info(f"已加载配置: {config}")
    
    # 确保监控文件夹存在
    watch_folder = config['watch_folder']
    os.makedirs(watch_folder, exist_ok=True)
    logging.info(f"监控文件夹: {watch_folder}")
    
    # 创建事件处理器
    event_handler = PrintHandler(config)
    
    # 创建观察者并启动
    observer = Observer()
    observer.schedule(event_handler, watch_folder, recursive=True)
    observer.start()
    
    logging.info(f"已开始监控文件夹: {watch_folder}")
    logging.info(f"支持的文件类型: {config['supported_extensions']}")
    logging.info(f"默认打印机: {config['default_printer']}")
    
    try:
        while True:
            time.sleep(1)
    except KeyboardInterrupt:
        logging.info("服务正在停止...")
        observer.stop()
        # 保存最终状态
        event_handler.save_print_status()
    
    observer.join()
    logging.info("服务已停止")

if __name__ == "__main__":
    run_service()